package task

import (
	"context"
	"errors"
	"strings"
	"sync"
	"time"

	log2 "sieve_engine/execute/whatsapp/log"
	"sieve_engine/lib/config"
	"sieve_engine/utils"

	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
	errors2 "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/regions"
	tencentai "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/iai/v20200303"
	"go.uber.org/zap"
	"golang.org/x/time/rate"
)

var (
	tencentAiConfig = TencentAiConfig{
		Credentials: []*CredentialsConfig{
			{
				Appid:     *config.String("tencent_ai.credentials.appid", "1252122934", "tencent ai credentials appid"),
				SecretId:  *config.String("tencent_ai.credentials.secret_id", "AKIDELH90sdO4at7FJYzRrCThqoa8iBWzPzR", "tencent ai credentials secret_id"),
				SecretKey: *config.String("tencent_ai.credentials.secret_key", "WrCQezGLTSRL5pPl9RaZLvRwd9Rm393l", "tencent ai credentials secret_key"),
			},
		},
		HandlerCount:         *config.Int("tencent_ai.handler_count", 10, "whatsapp auth handler count"),
		MaxFaceNum:           uint64(*config.Int("tencent_ai.max_face_num", 2, "tencent ai max_face_num")),
		MinFaceSize:          uint64(*config.Int("tencent_ai.min_face_size", 10, "tencent ai min_face_size")),
		NeedFaceAttributes:   uint64(*config.Int("tencent_ai.need_face_attributes", 1, "tencent ai need_face_attributes")),
		NeedQualityDetection: uint64(*config.Int("tencent_ai.need_quality_detection", 0, "tencent ai need_quality_detection")),
		FaceModelVersion:     *config.String("tencent_ai.face_model_version", "3.0", "tencent ai face_model_version"),
		NeedRotateDetection:  uint64(*config.Int("tencent_ai.need_rotate_detection", 0, "tencent ai need_rotate_detection")),
		CacheSize:            int32(*config.Int("tencent_ai.cache_size", 5000, "tencent ai cache_size")),
		FailedTryCount:       *config.Int("tencent_ai.failed_try_count", 3, "tencent ai failed_try_count"),
		RateLimitSize:        int32(*config.Int("tencent_ai.rate_limit_size", 50, "tencent ai rate_limit_size")),
	}
)

type TencentAiConfig struct {
	Credentials          []*CredentialsConfig `json:"credentials"  mapstructure:"credentials"`
	HandlerCount         int                  `json:"handler_count"  mapstructure:"handler_count"`
	MaxFaceNum           uint64               `json:"max_face_num" mapstructure:"max_face_num"`
	MinFaceSize          uint64               `json:"min_face_size" mapstructure:"min_face_size"`
	NeedFaceAttributes   uint64               `json:"need_face_attributes" mapstructure:"need_face_attributes"`
	NeedQualityDetection uint64               `json:"need_quality_detection" mapstructure:"need_quality_detection"`
	FaceModelVersion     string               `json:"face_model_version" mapstructure:"face_model_version"`
	NeedRotateDetection  uint64               `json:"need_rotate_detection" mapstructure:"need_rotate_detection"`
	CacheSize            int32                `json:"cache_size"  mapstructure:"cache_size"`
	FailedTryCount       int                  `json:"failed_try_count"  mapstructure:"failed_try_count"`
	RateLimitSize        int32                `json:"rate_limit_size"   mapstructure:"rate_limit_size"`
}

type CredentialsConfig struct {
	Appid     string `json:"appid" mapstructure:"appid"`
	SecretId  string `json:"secret_id" mapstructure:"secret_id"`
	SecretKey string `json:"secret_key"  mapstructure:"secret_key"`
}

type tencentAiServiceProvider struct {
	closec    chan int
	requestsc chan *aiRequest
	syncer    *sync.WaitGroup
	logger    *zap.Logger
	ctx       context.Context
}

type aiRequest struct {
	pic      string
	resultc  chan *detectResult
	tryCount int
}

type detectResult struct {
	result *TencentAiDetectResult
	err    error
	noFace bool
}

var tencentAiServiceProviderOnce sync.Once

var TencentAiServerImpl *tencentAiServiceProvider

func NewTencentAiServiceProvider(closeChan chan int, syncer *sync.WaitGroup) *tencentAiServiceProvider {
	tencentAiServiceProviderOnce.Do(func() {
		TencentAiServerImpl = &tencentAiServiceProvider{
			closec:    closeChan,
			requestsc: make(chan *aiRequest, tencentAiConfig.CacheSize),
			syncer:    syncer,
			logger:    log2.CreateLogger(closeChan, "tencent_ai"),
			ctx:       context.Background(),
		}
		TencentAiServerImpl.schedule()
	})

	return TencentAiServerImpl
}

func (provider *tencentAiServiceProvider) schedule() {

	// log2.SievesLog.Info("NewTencentAiServiceProvider started",
	// 	zap.Int32("credentials", int32(len(tencentAiConfig.Credentials))),
	// 	zap.Int("handlerCount", tencentAiConfig.HandlerCount))
	//
	// log2.SievesLog.Info("tencentAiService started")

	for _, credential := range tencentAiConfig.Credentials {
		limiter := rate.NewLimiter(rate.Every(time.Second), int(tencentAiConfig.RateLimitSize))
		for i := 0; i < tencentAiConfig.HandlerCount; i++ {
			func(cred *CredentialsConfig) {
				client, err := TencentAiServerImpl.createClients(cred.SecretId, cred.SecretKey)
				if err != nil {
					TencentAiServerImpl.logger.Error("TencentAiServerImpl.createClients failed", zap.Error(err))
					return
				}
				go func() {
					for {
						select {
						case req := <-TencentAiServerImpl.requestsc:
							_ = limiter.Wait(TencentAiServerImpl.ctx)
							result, err := TencentAiServerImpl.detect(req.pic, client)
							if err != nil {
								if err == limitError || err == netError || err == timeoutError {
									if req.tryCount < tencentAiConfig.FailedTryCount {
										req.tryCount += 1
										TencentAiServerImpl.requestsc <- req
										<-time.After(time.Millisecond * time.Duration(utils.RandInt(100, 500)))
									} else {
										TencentAiServerImpl.logger.Error("detectFailed", zap.Error(err), zap.String("pic", req.pic))
										req.resultc <- &detectResult{err: err, result: result}
									}
								} else if err == noFacesError {
									req.resultc <- &detectResult{err: err, result: result, noFace: true}
								} else {
									req.resultc <- &detectResult{err: err, result: result}
								}
							} else {
								TencentAiServerImpl.logger.Error("detectSuccess", zap.String("pic", req.pic), zap.Reflect("result", result))
								req.resultc <- &detectResult{err: err, result: result}
							}
						}
					}
				}()
			}(credential)
		}
	}
}

type TencentAiDetectResult struct {
	Gender   int32
	Age      int32
	multiple bool
}

func (provider *tencentAiServiceProvider) RequestDetect(pic string) *detectResult {
	provider.logger.Info("RequestDetect received")
	req := &aiRequest{resultc: make(chan *detectResult, 1), pic: pic}
	provider.requestsc <- req
	return <-req.resultc
}

var inactiveError = errors.New("inactive")
var noFacesError = errors.New("no faces")
var netError = errors.New("net error")
var limitError = errors.New("rate limit")
var timeoutError = errors.New("timeout")

func (provider *tencentAiServiceProvider) detect(url string, client *tencentai.Client) (*TencentAiDetectResult, error) {

	start := time.Now()
	url = strings.ReplaceAll(url, "\\u0026", "&")

	detectFaceRequest := tencentai.NewDetectFaceRequest()

	detectFaceRequest.MaxFaceNum = &tencentAiConfig.MaxFaceNum
	detectFaceRequest.MinFaceSize = &tencentAiConfig.MinFaceSize
	detectFaceRequest.NeedFaceAttributes = &tencentAiConfig.NeedFaceAttributes     // 是否需要返回人脸属性信息（FaceAttributesInfo）。0 为不需要返回，1 为需要返回。默认为 0
	detectFaceRequest.NeedQualityDetection = &tencentAiConfig.NeedQualityDetection // 是否开启质量检测。0 为关闭，1 为开启。默认为 0。
	detectFaceRequest.FaceModelVersion = &tencentAiConfig.FaceModelVersion         // 人脸识别服务所用的算法模型版本。目前入参支持 “2.0”和“3.0“ 两个输入
	detectFaceRequest.NeedRotateDetection = &tencentAiConfig.NeedRotateDetection   // 是否开启图片旋转识别支持。0为不开启，1为开启。默认为0

	detectFaceRequest.Url = common.StringPtr(url)

	faceResponse, err := client.DetectFaceWithContext(context.Background(), detectFaceRequest)

	provider.logger.Info("DetectProfile", zap.Int64("delta", time.Now().Sub(start).Milliseconds()))

	if err != nil {
		if _, ok := err.(*errors2.TencentCloudSDKError); ok {
			sdkError := err.(*errors2.TencentCloudSDKError)
			// cancel或者网络原因
			if sdkError.Code == "ClientError.NetworkError" /* || sdkError.Code == "FailedOperation.RequestTimeout"*/ {
				return nil, netError
			}
			if !strings.Contains(sdkError.Code, "InvalidParameterValue.NoFaceInPhoto") {
				return nil, noFacesError
			}
			// 帐号异常
			if strings.Contains(sdkError.Code, "ResourceUnavailable") || sdkError.Code == "AuthFailure.InvalidAuthorization" {
				return nil, sdkError
			}
			// 后端服务超时
			if sdkError.Code == "FailedOperation.RequestTimeout" {
				return nil, timeoutError
			}
			// 请求频率过快
			if sdkError.Code == "RequestLimitExceeded" || sdkError.Code == "FailedOperation.RequestLimitExceeded" {
				return nil, limitError
			}

			return nil, err
		}
		// 非SDK异常，直接失败。实际代码中可以加入其他的处理。
		return nil, err
	}

	if len(faceResponse.Response.FaceInfos) == 0 {
		return nil, noFacesError
	}

	attributesInfo := faceResponse.Response.FaceInfos[0].FaceAttributesInfo
	return &TencentAiDetectResult{Gender: int32(*(attributesInfo.Gender)), Age: int32(*attributesInfo.Age), multiple: len(faceResponse.Response.FaceInfos) > 1}, nil
}

func (provider *tencentAiServiceProvider) createClients(secretid string, secretkey string) (*tencentai.Client, error) {
	credential := common.NewCredential(secretid, secretkey)

	// 非必要步骤
	// 实例化一个客户端配置对象，可以指定超时时间等配置
	cpf := profile.NewClientProfile()
	// SDK默认使用POST方法。
	// 如果你一定要使用GET方法，可以在这里设置。GET方法无法处理一些较大的请求。
	// 如非必要请不要修改默认设置。
	cpf.HttpProfile.ReqMethod = "POST"
	// SDK有默认的超时时间，如非必要请不要修改默认设置。
	// 如有需要请在代码中查阅以获取最新的默认值。
	cpf.HttpProfile.ReqTimeout = 90
	// SDK会自动指定域名。通常是不需要特地指定域名的，但是如果你访问的是金融区的服务，
	// 则必须手动指定域名，例如云服务器的上海金融区域名： cvm.ap-shanghai-fsi.tencentcloudapi.com
	cpf.HttpProfile.Endpoint = "iai.ap-singapore.tencentcloudapi.com"
	// SDK默认用TC3-HMAC-SHA256进行签名，它更安全但是会轻微降低性能。
	// 如非必要请不要修改默认设置。
	cpf.SignMethod = "TC3-HMAC-SHA256"
	// SDK 默认用 zh-CN 调用返回中文。此外还可以设置 en-US 返回全英文。
	// 但大部分产品或接口并不支持全英文的返回。
	// 如非必要请不要修改默认设置。
	cpf.Language = "en-US"
	// 打印日志，默认是false
	cpf.Debug = false // TODO 关闭
	cpf.NetworkFailureMaxRetries = 3
	cpf.RateLimitExceededMaxRetries = 3

	// 实例化要请求产品(以cvm为例)的client对象
	// 第二个参数是地域信息，可以直接填写字符串ap-guangzhou，或者引用预设的常量
	client, err := tencentai.NewClient(credential, regions.Singapore, cpf)
	if err != nil {
		return nil, err
	} else {
		return client, nil
	}
}
